package com.example.apidocserver.infrastructure.util;

import com.alibaba.druid.util.StringUtils;
import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONException;
import com.alibaba.fastjson2.JSONObject;
import com.example.apidocserver.domain.document.entity.ApiInfo;
import com.example.apidocserver.domain.document.entity.ApiParam;
import com.example.apidocserver.domain.document.entity.ApiParamField;
import com.example.apidocserver.infrastructure.util.entity.ApiFieldUtil;
import com.example.apidocserver.infrastructure.util.result.ParamException;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.CollectionUtils;

import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.regex.Pattern;

import static com.example.apidocserver.infrastructure.constants.SystemConstants.*;

@Slf4j
public class ParamUtil {
    public static Integer getBaseType(Class<?> paramType) {
        if (String.class.equals(paramType)) {
            return 1;
        } else if (Number.class.equals(paramType)) {
            return 2;
        } else if (Boolean.class.equals(paramType)) {
            return 6;
        } else if (paramType.isPrimitive()) {
            if (boolean.class == paramType) {
                return 6;
            } else {
                return 2;
            }
        } else {
            return null;
        }
    }

    /**
     * 判断入参类型 * * @param body 入参对象 * @return 是否是JSONObject
     */
    public static boolean isObject(String body) {
        byte[] aByte = body.trim().getBytes(StandardCharsets.UTF_8);
        if (aByte[0] == '[') {
            return false;
        } else if (aByte[0] == '{') {
            return true;
        } else {
            throw new ParamException("body参数需要为对象。");
        }
    }

    /**
     * 判断入参类型 * * @param body 入参对象 * @return 是否是JSONObject
     */
    public static int isType(String body) {
        byte[] aByte = body.trim().getBytes(StandardCharsets.UTF_8);
        if (aByte[0] == '[') {
            return FT_ARR;
        } else if (aByte[0] == '{') {
            return FT_OBJ;
        } else {
            return getValueType(body);
        }
    }

    /**
     * 获取对象类型值
     *
     * @param value 对象
     * @return 值
     */
    public static Integer getValueType(Object value) {
        if (Objects.isNull(value)) {
            return FT_NULL;
        } else if (value instanceof JSONArray) {
            return FT_ARR;
        } else if (value instanceof Number) {
            return FT_NO;
        } else if (value instanceof String) {
            return FT_STR;
        } else if (value instanceof Boolean) {
            return FT_BLN;
        } else {
            return FT_OBJ;
        }
    }

    /**
     * 入参path字符串转字段说明对象
     *
     * @param pathStr path字符串
     * @param now     当前时间
     * @param apiInfo 接口数据
     * @return 字段说明集合
     */
    public static List<ApiParamField> changeInputPathField(String pathStr, Date now, ApiInfo apiInfo) {
        List<ApiParamField> result = new LinkedList<>();
        if (!NO_DATA.equals(pathStr)) {
            String pathParamStr = pathStr.trim();
            while (pathParamStr.indexOf("/") == 0) {
                pathParamStr = pathParamStr.substring(1);
            }
            String[] pathArr = pathParamStr.split("/");
            if (pathArr.length == 0) {
                return result;
            }
            for (String pathParam : pathArr) {
                if (!StringUtils.isEmpty(pathParam)) {
                    pathParam = pathParam.replace("{", "").replace("}", "");
                    ApiParamField apiParamField = new ApiParamField();
                    setFieldDef(now, pathParam, apiParamField, apiInfo);
                    result.add(apiParamField);
                }
            }
        }
        return result;
    }

    public static List<ApiParamField> changeInputQueryField(String paramStrIn, Date now, ApiInfo apiInfo) {
        List<ApiParamField> result = new LinkedList<>();
        if (!NO_DATA.equals(paramStrIn)) {
            String paramStr = paramStrIn.trim();
            if (paramStr.getBytes(StandardCharsets.UTF_8)[0] == QUERY_START) {
                paramStr = paramStr.substring(1);
            }
            String[] queryParams = paramStr.split(QUERY_AND_STR);
            for (String queryParam : queryParams) {
                String[] paramArr = queryParam.split("=");
                if (paramArr.length == 0) {
                    continue;
                }
                if (StringUtils.isEmpty(paramArr[0])) {
                    throw new ParamException("query参数名称不能为空");
                }

                ApiParamField apiParamField = new ApiParamField();
                setFieldDef(now, paramArr[0], apiParamField, apiInfo);
                result.add(apiParamField);
            }
        }
        return result;
    }

    public static List<ApiParamField> changeHeaderField(String paramStrIn, Date now, ApiInfo apiInfo) {
        List<ApiParamField> result = new LinkedList<>();
        if (!NO_DATA.equals(paramStrIn)) {
            String paramStr = paramStrIn.trim();
            if (StringUtils.isEmpty(paramStr)) {
                return result;
            }
            String[] queryParams = paramStr.split("\n");
            for (String queryParam : queryParams) {
                String[] paramArr = queryParam.split(":");
                if (StringUtils.isEmpty(paramArr[0])) {
                    throw new ParamException("header参数名称不能为空");
                }
                ApiParamField apiParamField = new ApiParamField();
                setFieldDef(now, paramArr[0], apiParamField, apiInfo);
                result.add(apiParamField);
            }
        }
        return result;
    }

    /**
     * 将入参body字符串转换为参数说明对象和字段说明对象
     *
     * @param bodyIn  body体字符串
     * @param now     当前时间
     * @param apiInfo 接口信息
     * @return 参数路径对应参数说明对象
     */
    public static Map<String, ApiParam> getBodyParam(String bodyIn, Date now, ApiInfo apiInfo) {
        // 1.创建返回值对象和校验入参  TODO 限制同参数字段重复
        Map<String, ApiParam> fieldParamMap = new HashMap<>(8);
        String bodyStr = bodyIn.trim();
        if (StringUtils.isEmpty(bodyStr)) {
            return fieldParamMap;
        }

        // 2.转参数说明和字段说明
        int isObj = isType(bodyStr);
        Map<String, Set<String>> fieldKeyMap = new HashMap<>(8);
        try {
            if (isObj == FT_OBJ) {
                JSONObject jsonObject = JSONObject.parseObject(bodyStr);
                setBodyParam(null, jsonObject, fieldParamMap, 1, fieldKeyMap, "body", now, apiInfo);
            } else if (isObj == FT_ARR) {
                JSONArray jsonArray = JSONArray.parseArray(bodyStr);
                setBodyParams(null, jsonArray, fieldParamMap, 1, fieldKeyMap, "body.[]", now, apiInfo);
            } else {
                ApiParamField filed = new ApiParamField().setFieldTypeId(isObj);
                List<ApiParamField> fields = new ArrayList<>(1);
                fields.add(filed);
                ApiParam apiParam = getApiParam(fields, apiInfo, PT_BODY);
                setFieldDef(now, "基础数据类型", filed, apiInfo);
                fieldParamMap.put("body", apiParam);
            }
            return fieldParamMap;
        } catch (JSONException jsonException) {
            throw new ParamException("body参数不符合json规范");
        }
    }

    /**
     * 获取参数说明对象
     *
     * @param apiParamFields 字段说明对象集合
     * @param apiInfo        接口信息
     * @param ptType         参数类型
     * @return 参数说明对象
     */
    public static ApiParam getApiParam(List<ApiParamField> apiParamFields, ApiInfo apiInfo, Integer ptType) {
        return new ApiParam().setFields(apiParamFields).setApiId(apiInfo.getId())
                .setParamType(ptType).setDataType(DT_INPUT).setEnableFlag(YES).setParamLevel(YES)
                .setParamDesc(NO_DATA).setTitle("").setParamPath("").setOrderNo(YES);
    }

    /**
     * 设置默认字段说明对象
     *
     * @param now           当前时间
     * @param fieldName     字段名称
     * @param apiParamField 字段类型
     * @param apiInfo       接口信息
     */
    public static void setFieldDef(Date now, String fieldName, ApiParamField apiParamField, ApiInfo apiInfo) {
        String fn = fieldName.trim();
        apiParamField.setFieldName(fn).setFieldTypeId(FT_STR).setFieldMust(STR_YES).setEnableFlag(YES).setUsePriority(YES)
                .setSpaceId(apiInfo.getSpaceId()).setApiId(apiInfo.getId()).setFieldDesc(fn)
                .setParamFieldDesc("").setDescMode(DM_AFTER).setCreateTime(now).setUpdateTime(now);
    }

    private static void setBodyParam(String fieldIn, JSONObject paramJsIn, Map<String, ApiParam> fieldParamMap,
                                     int lv, Map<String, Set<String>> fieldKeyMap, String paramPath, Date now,
                                     ApiInfo apiInfo) {
        // 1.获取相同路径参数对象和对应字段key set
        ApiParam rltApiParamInput = fieldParamMap.get(paramPath);
        Set<String> fieldKeySet = fieldKeyMap.get(fieldIn);

        if (Objects.isNull(rltApiParamInput)) {
            // 2.参数路径第一次出现
            rltApiParamInput = new ApiParam();
            rltApiParamInput.setParamLevel(lv).setParamDesc(NO_DATA).setOrderNo(fieldParamMap.size() + 1)
                    .setTitle((Objects.isNull(fieldIn) ? "body参数" : fieldIn) + "说明").setParamPath(paramPath)
                    .setEnableFlag(YES);

            fieldParamMap.put(paramPath, rltApiParamInput);
            fieldKeySet = new HashSet<>(paramJsIn.keySet().size());
            fieldKeyMap.put(fieldIn, fieldKeySet);
        }

        // 3.参数示例对象转换为字段说明对象
        rltApiParamInput.setFields(setObjFieldDo(paramJsIn, fieldParamMap, lv, fieldKeyMap, fieldKeySet,
                paramPath, now, apiInfo));
    }

    private static void setBodyParams(String fieldIn, JSONArray paramsJsIn, Map<String, ApiParam> fieldParamMap,
                                      int lv, Map<String, Set<String>> fieldKeyMap, String paramPath, Date now,
                                      ApiInfo apiInfo) {
        // 1.如果数据为空，则不做处理
        if (CollectionUtils.isEmpty(paramsJsIn)) {
            return;
        }

        // 2.为基础数据对象，可以不带说明
        Integer valueTypeIn = getValueType(paramsJsIn.get(0));
        if (FT_OBJ != valueTypeIn && FT_ARR != valueTypeIn) {
            return;
        }

        // 3.获取同路径参数说明对象
        ApiParam rltApiParamInput = fieldParamMap.get(paramPath);
        Set<String> fieldKeySet = fieldKeyMap.get(fieldIn);
        if (Objects.isNull(rltApiParamInput)) {
            rltApiParamInput = new ApiParam();
            rltApiParamInput.setFields(new LinkedList<>()).setParamLevel(lv).setParamDesc(NO_DATA).setOrderNo(fieldParamMap.size())
                    .setTitle((Objects.isNull(fieldIn) ? "body参数" : fieldIn) + "数组说明").setParamPath(paramPath)
                    .setEnableFlag(YES);
            fieldParamMap.put(paramPath, rltApiParamInput);

            fieldKeySet = new HashSet<>();
            fieldKeyMap.put(fieldIn, fieldKeySet);
        }

        // 4.转化同数组不同字段
        setFiledByArrBrother(paramsJsIn, fieldParamMap, lv, fieldKeyMap, paramPath, now, apiInfo,
                rltApiParamInput, fieldKeySet);
    }

    private static void setFiledByArrBrother(JSONArray paramsJsIn, Map<String, ApiParam> fieldParamMap, int lv,
                                             Map<String, Set<String>> fieldKeyMap, String paramPath, Date now,
                                             ApiInfo apiInfo, ApiParam rltApiParamInput, Set<String> fieldKeySet) {
        Set<String> fieldKeyArrSet = new HashSet<>();
        for (int i = 0; i < paramsJsIn.size(); i++) {
            // 判断是否是二维数组
            Integer valueType = getValueType(paramsJsIn.get(i));
            if (FT_ARR == valueType) {
                setFiledByArrBrother(paramsJsIn.getJSONArray(i), fieldParamMap, lv + 1, fieldKeyMap,
                        paramPath + ".[].[]", now, apiInfo, rltApiParamInput, fieldKeySet);
                continue;
            }

            // 1.对象转字符串说明对象
            List<ApiParamField> apiParamFields = setObjFieldDo(paramsJsIn.getJSONObject(i), fieldParamMap, lv,
                    fieldKeyMap, fieldKeySet, paramPath, now, apiInfo);

            // 2.排除相同字段类型
            for (ApiParamField apiParamField : apiParamFields) {
                String fieldKey = ApiFieldUtil.getFieldKey(apiParamField);
                if (!fieldKeyArrSet.contains(fieldKey)) {
                    fieldKeyArrSet.add(fieldKey);
                    rltApiParamInput.getFields().add(apiParamField);
                }
            }
        }
    }

    private static List<ApiParamField> setObjFieldDo(JSONObject paramJsIn, Map<String, ApiParam> fieldParamMap,
                                                     int lv, Map<String, Set<String>> fieldKeyMap,
                                                     Set<String> fieldKeySet, String paramPath, Date now, ApiInfo apiInfo) {
        List<ApiParamField> resultFiledList = new ArrayList<>();
        for (String field : paramJsIn.keySet()) {
            Integer valueType = getValueType(paramJsIn.get(field));
            if (!containsFieldKey(fieldKeySet, field, valueType)) {
                ApiParamField paramField = new ApiParamField();
                setFieldDef(now, field, paramField, apiInfo);
                paramField.setFieldDesc(field).setFieldTypeId(valueType);
                resultFiledList.add(paramField);
                if (FT_OBJ == valueType) {
                    setBodyParam(field, paramJsIn.getJSONObject(field), fieldParamMap, lv + 1, fieldKeyMap,
                            "".equals(paramPath) ? field : paramPath + "." + field, now, apiInfo);
                } else if (FT_ARR == valueType) {
                    setBodyParams(field, paramJsIn.getJSONArray(field), fieldParamMap, lv + 1, fieldKeyMap,
                            paramPath + ".[]", now, apiInfo);
                }
            }
        }
        return resultFiledList;
    }

    private static boolean containsFieldKey(Set<String> fieldKeySet, String field, Integer valueType) {
        String keyPre = field + SEPARATOR_FIELD;
        String key = keyPre + valueType;
        if (!CollectionUtils.isEmpty(fieldKeySet) && fieldKeySet.contains(key)) {
            return true;
        } else {
            if (FT_NULL == valueType) {
                for (int i = 1; i < 6; i++) {
                    String key1 = keyPre + i;
                    if (fieldKeySet.contains(key1)) {
                        return true;
                    }
                }
                return false;
            } else {
                return false;
            }
        }
    }

    public static String pathParamStandard(String path) {
        boolean standardFlag = !StringUtils.isEmpty(path) && (!path.contains("http://") || !path.contains("https://"));
        if (standardFlag) {
            path = path.trim();
            if (path.indexOf(PATH_BASE_STR) != 0) {
                path = PATH_BASE_STR + path;
                path = path.replace("//", PATH_BASE_STR);
            }
        }
        return path;
    }

    private final static Pattern pattern = Pattern.compile("^[\u4e00-\u9fa5a-zA-Z0-9_\\s]+$");

    /**
     * 校验字符串只能包含中文、字母大小写、数组
     *
     * @param world 校验字段
     * @return 是否校验通过
     */
    public static boolean matherStr(String world) {
        return pattern.matcher(world).matches();
    }
}